package org.reso.certification.codegen;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.usermodel.Sheet;
import org.reso.commander.common.Utils;
import org.reso.models.ReferenceStandardField;

import static org.reso.certification.codegen.DDLProcessor.buildDbTableName;
import static org.reso.certification.containers.WebAPITestContainer.EMPTY_STRING;

public class ResourceInfoProcessor extends WorksheetProcessor {
  final static String
      ANNOTATION_TERM_DISPLAY_NAME = "RESO.OData.Metadata.StandardName",
      ANNOTATION_TERM_DESCRIPTION = "Core.Description",
      ANNOTATION_TERM_URL = "RESO.DDWikiUrl";
  private static final Logger LOG = LogManager.getLogger(ResourceInfoProcessor.class);
  private static final String
      FILE_EXTENSION = ".java";

  public void processResourceSheet(Sheet sheet) {
    super.processResourceSheet(sheet);
    markup.append(ResourceInfoTemplates.buildClassInfo(sheet.getSheetName(), null));
  }

  @Override
  void processNumber(ReferenceStandardField row) {
    markup.append(ResourceInfoTemplates.buildNumberMarkup(row));
  }

  @Override
  void processStringListSingle(ReferenceStandardField row) {
    markup.append(ResourceInfoTemplates.buildStringListSingleMarkup(row));
  }

  @Override
  void processString(ReferenceStandardField row) {
    markup.append(ResourceInfoTemplates.buildStringMarkup(row));
  }

  @Override
  void processBoolean(ReferenceStandardField row) {
    markup.append(ResourceInfoTemplates.buildBooleanMarkup(row));
  }

  @Override
  void processStringListMulti(ReferenceStandardField row) {
    markup.append(ResourceInfoTemplates.buildStringListMultiMarkup(row));
  }

  @Override
  void processDate(ReferenceStandardField row) {
    markup.append(ResourceInfoTemplates.buildDateMarkup(row));
  }

  @Override
  void processTimestamp(ReferenceStandardField row) {
    markup.append(ResourceInfoTemplates.buildTimestampMarkup(row));
  }

  @Override
  void processCollection(ReferenceStandardField row) {
    LOG.debug("Collection Type is not supported!");
  }

  @Override
  void generateOutput() {
    LOG.info("Using reference worksheet: " + REFERENCE_WORKSHEET);
    LOG.info("Generating ResourceInfo .java files for the following resources: " + resourceTemplates.keySet().toString());
    resourceTemplates.forEach((resourceName, content) -> {
      //put in local directory rather than relative to where the input file is
      Utils.createFile(getDirectoryName(), resourceName + "Definition" + FILE_EXTENSION, content);
    });
  }

  @Override
  String getDirectoryName() {
    return startTimestamp + "-ResourceInfoModels";
  }

  @Override
  public void afterResourceSheetProcessed(Sheet sheet) {
    assert sheet != null && sheet.getSheetName() != null;
    String resourceName = sheet.getSheetName();

    String templateContent =
        markup.toString() + "\n" +
            "    return " + resourceName + "Definition.fieldList;\n" +
            "  }\n" +
            "}";

    resourceTemplates.put(resourceName, templateContent);
    resetMarkupBuffer();
  }

  public static final class ResourceInfoTemplates {
    /**
     * Contains various templates used for test generation
     * TODO: add a formatter rather than using inline spaces
     */
    public static String buildClassInfo(String resourceName, String generatedTimestamp) {
      if (resourceName == null) return null;
      if (generatedTimestamp == null) generatedTimestamp = Utils.getIsoTimestamp();

      final String definitionName = resourceName + "Definition";

      return "package org.reso.service.data.definition;\n" + "\n" +
          "import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;\n" +
          "import org.reso.service.data.meta.FieldInfo;\n" +
          "import org.reso.service.data.meta.ResourceInfo;\n" + "\n" +
          "import java.util.ArrayList;\n" + "\n" +
          "// This class was autogenerated on: " + generatedTimestamp + "\n" +
          "public class " + definitionName + " extends ResourceInfo {\n" +
          "  private static ArrayList<FieldInfo> fieldList = null;\n" + "\n" +
          "  public " + definitionName + "() {" + "\n" +
          "    this.tableName = " + buildDbTableName(resourceName) + ";\n" +
          "    this.resourcesName = " + resourceName + ";\n" +
          "    this.resourceName = " + resourceName + ";\n" +
          "  }\n" + "\n" +
          "  public ArrayList<FieldInfo> getFieldList() {\n" +
          "    return " + definitionName + ".getStaticFieldList();\n" +
          "  }\n" + "\n" +
          "  public static ArrayList<FieldInfo> getStaticFieldList() {\n" +
          "    if (null != " + definitionName + ".fieldList) {\n" +
          "      return " + definitionName + ".fieldList;\n" +
          "    }\n" + "\n" +
          "    ArrayList<FieldInfo> list = new ArrayList<FieldInfo>();\n" +
          "    " + definitionName + ".fieldList = list;\n" +
          "    FieldInfo fieldInfo = null;\n";
    }

    public static String buildBooleanMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;

      //TODO: refactor into one method that takes a type name and returns the appropriate content
      return "\n" +
          "    fieldInfo = new FieldInfo(\"" + field.getStandardName() + "\", EdmPrimitiveTypeKind.Boolean.getFullQualifiedName());\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDisplayName() + "\", \"" + ANNOTATION_TERM_DISPLAY_NAME + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDefinition() + "\", \"" + ANNOTATION_TERM_DESCRIPTION + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getWikiPageUrl() + "\", \"" + ANNOTATION_TERM_URL + "\");\n" +
          "    list.add(fieldInfo);" +
          "\n";
    }

    public static String buildDateMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;

      return "\n" +
          "    fieldInfo = new FieldInfo(\"" + field.getStandardName() + "\", EdmPrimitiveTypeKind.Date.getFullQualifiedName());\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDisplayName() + "\", \"" + ANNOTATION_TERM_DISPLAY_NAME + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDefinition() + "\", \"" + ANNOTATION_TERM_DESCRIPTION + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getWikiPageUrl() + "\", \"" + ANNOTATION_TERM_URL + "\");\n" +
          "    list.add(fieldInfo);" +
          "\n";
    }

    /**
     * Provides special routing for Data Dictionary numeric types, which may be Integer or Decimal
     *
     * @param field the numeric field to build type markup for
     * @return a string containing specific markup for the given field
     */
    public static String buildNumberMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;

      if (field.getSuggestedMaxPrecision() != null) return buildDecimalMarkup(field);
      else return buildIntegerMarkup(field);
    }

    public static String buildDecimalMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;

      return "\n" +
          "    fieldInfo = new FieldInfo(\"" + field.getStandardName() + "\", EdmPrimitiveTypeKind.Decimal.getFullQualifiedName());\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDisplayName() + "\", \"" + ANNOTATION_TERM_DISPLAY_NAME + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDefinition() + "\", \"" + ANNOTATION_TERM_DESCRIPTION + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getWikiPageUrl() + "\", \"" + ANNOTATION_TERM_URL + "\");\n" +
          "    list.add(fieldInfo);" +
          "\n";

      //TODO: Length is actually scale for Decimal fields by the DD! :/
      //TODO: Add setScale property to Decimal types in FieldInfo

      //TODO: Precision is actually Scale for Decimal fields by the DD! :/
      //TODO: Add setPrecision property to Decimal types in FieldInfo
    }

    public static String buildIntegerMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;

      return "\n" +
          "    fieldInfo = new FieldInfo(\"" + field.getStandardName() + "\", EdmPrimitiveTypeKind.Int64.getFullQualifiedName());\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDisplayName() + "\", \"" + ANNOTATION_TERM_DISPLAY_NAME + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDefinition() + "\", \"" + ANNOTATION_TERM_DESCRIPTION + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getWikiPageUrl() + "\", \"" + ANNOTATION_TERM_URL + "\");\n" +
          "    list.add(fieldInfo);" +
          "\n";
    }

    private static String buildStandardEnumerationMarkup(String lookupName) {
      //TODO: add code to build Lookups
      return "\n    /* TODO: buildStandardEnumerationMarkup */\n";
    }

    public static String buildStringListMultiMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;
      //TODO: add multi lookup handler
      return "\n    /* TODO: buildStringListMultiMarkup */\n";
    }

    public static String buildStringListSingleMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;
      //TODO: add single lookup handler
      return "\n    /* TODO: buildStringListSingleMarkup */\n";
    }

    public static String buildStringMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;

      String content = "\n" +
          "    fieldInfo = new FieldInfo(\"" + field.getStandardName() + "\", EdmPrimitiveTypeKind.String.getFullQualifiedName());\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDisplayName() + "\", \"" + ANNOTATION_TERM_DISPLAY_NAME + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDefinition() + "\", \"" + ANNOTATION_TERM_DESCRIPTION + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getWikiPageUrl() + "\", \"" + ANNOTATION_TERM_URL + "\");\n";

      if (field.getSuggestedMaxLength() != null) {
        content +=
            "    fieldInfo.setMaxLength(" + field.getSuggestedMaxLength() + ");\n";
      }

      content +=
          "    list.add(fieldInfo);" + "\n";

      return content;
    }

    public static String buildTimestampMarkup(ReferenceStandardField field) {
      if (field == null) return EMPTY_STRING;

      return "\n" +
          "    fieldInfo = new FieldInfo(\"" + field.getStandardName() + "\", EdmPrimitiveTypeKind.DateTime.getFullQualifiedName());\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDisplayName() + "\", \"" + ANNOTATION_TERM_DISPLAY_NAME + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getDefinition() + "\", \"" + ANNOTATION_TERM_DESCRIPTION + "\");\n" +
          "    fieldInfo.addAnnotation(\"" + field.getWikiPageUrl() + "\", \"" + ANNOTATION_TERM_URL + "\");\n" +
          "    list.add(fieldInfo);" +
          "\n";
    }
  }
}
